import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { t } from '@/i18n'; import { api, isNotBuilt, isNotFound, safe } from '@/lib/api'; import { routes } from '@/lib/site'; import { regionShort } from '@/lib/regions'; import { TOPICS, isTopicId, topicById } from '@/lib/topics'; import type { CountryTopicResponse, MetricValue, SeriesResponse } from '@/lib/types'; import { IndicatorRow } from '@/components/country/indicator-row'; import { TopicCompact } from '@/components/country/topic-compact'; import { subtopicAnchor } from '@/lib/anchors'; import { CollapsibleGroup, SubtopicJumpNav } from '@/components/data/collapsible-group'; import { NotBuiltState } from '@/components/data/empty-state'; import { Section } from '@/components/data/section'; import { TopicNav } from '@/components/data/topic-nav'; export const revalidate = 900; type Params = { slug: string; topic: string }; const EAGER = 4; // charts rendered with server-fetched full history; the rest mount on scroll const SECTION_THRESHOLD = 12; // above this many indicators with data → collapsible subtopic blocks const OPEN_BLOCKS = 2; // blocks expanded by default on a sectioned topic page async function load(slug: string, topic: string): Promise { if (!isTopicId(topic)) return null; try { return await api.countryTopic(slug, topic); } catch (e) { if (isNotFound(e)) return null; if (isNotBuilt(e)) return 'not-built'; throw e; } } export async function generateMetadata({ params }: { params: Promise }): Promise { const { slug, topic } = await params; const data = await load(slug, topic); if (!data || data === 'not-built') return { title: t('topic.notFound'), robots: { index: false } }; const country = data.country.name ?? slug; const list = data.subtopics .flatMap((b) => b.indicators) .slice(0, 4) .map((m) => (m.indicator_name ?? m.indicator).toLowerCase()) .join(', '); const title = t('topic.title', { country, topic: data.topic.name }); const canonical = routes.countryTopic(data.country.slug ?? slug, topic); return { title, description: t('topic.description', { topic: data.topic.name, country, list }), alternates: { canonical }, openGraph: { title: `${title} — ${t('site.name')}`, url: canonical, type: 'article' }, }; } export default async function CountryTopicPage({ params }: { params: Promise }) { const { slug, topic } = await params; const data = await load(slug, topic); if (data === null) notFound(); if (data === 'not-built') return ; const c = data.country; const countryRef = { id: c.id, slug: c.slug ?? slug, name: c.name ?? c.id, flag: c.flag }; const all = data.subtopics.flatMap((b) => b.indicators); const withData = all.filter((m) => m.has_data); const noData = all.filter((m) => !m.has_data); const blocks = data.subtopics.map((b) => ({ ...b, rows: b.indicators.filter((m) => m.has_data) })).filter((b) => b.rows.length > 0); // Long topics (> 12 indicators with data, ≥ 2 subtopics): collapsible subtopic blocks, first two expanded. const sectioned = withData.length > SECTION_THRESHOLD && blocks.length >= 2; const openBlocks = sectioned ? blocks.slice(0, OPEN_BLOCKS) : blocks; const eagerIds = openBlocks .flatMap((b) => b.rows) .slice(0, EAGER) .map((m) => m.indicator); const eagerSeries = await Promise.all(eagerIds.map((iid) => safe(api.countrySeries(c.id, iid)))); const seriesById = new Map(eagerIds.map((iid, i) => [iid, eagerSeries[i] ?? null])); const def = topicById(topic); const others = TOPICS.filter((tp) => tp.id !== topic); return ( <>

{countryRef.name} · {data.topic.name}

{data.topic.blurb ?? def?.blurb}

{t('topic.indicators', { n: data.n_indicators })} · {t('topic.withData', { n: data.n_with_data })}

{sectioned ? ({ id: subtopicAnchor('sub', b.subtopic), label: b.subtopic, count: b.rows.length }))} label={t('topic.jump')} className="mt-3" /> : null} {blocks.map((block, i) => { const id = subtopicAnchor('sub', block.subtopic); const rowsEl = (
{block.rows.map((m: MetricValue) => ( ))}
); if (sectioned) return ( }> {rowsEl} ); return (
{rowsEl}
); })} {noData.length ? (
› {t('topic.noData', { n: noData.length })}

{t('topic.noDataHint', { country: countryRef.name })}

    {noData.map((m) => (
  • {m.indicator_name ?? m.indicator} {t('common.noData')}
  • ))}
) : null}
    {others.map((tp) => (
  • {tp.short}
  • ))}
); }